from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.api import memcache
from google.appengine.ext import blobstore

import string
import logging
import timezones
import settings

from datetime import *

class Periodical(db.Model):  
    name = db.StringProperty(required=True)   
    homepage = db.LinkProperty()
    category = db.CategoryProperty()
    last_update = db.DateTimeProperty(required=True)
    active = db.BooleanProperty(required=True, default=True)
    filename = db.StringProperty(required=True)
    filesizeKB = db.IntegerProperty()
    file = db.BlobProperty(default=None)
    contact = db.EmailProperty()
    created = db.DateTimeProperty(auto_now_add=True) 
    modified = db.DateTimeProperty(auto_now=True) 
    blobfile = blobstore.BlobReferenceProperty(blobstore.BlobKey, required=False)
    feed = db.LinkProperty()
    feedUpdated = db.DateTimeProperty()    
    
    # gets a list of active feeds from memcache
    @staticmethod
    def allPeriodicals():        
        periodicals = memcache.get(settings.MEM_PERIODICALS)
        if periodicals is None:            
            query = Periodical.all()
            query.order("name")                      
            periodicals = query.fetch(100) #watch out here!
            memcache.set(settings.MEM_PERIODICALS, periodicals)
        
        return periodicals
    
    @staticmethod
    def allActive():
        periodicals = memcache.get(settings.MEM_PERIODICALS_ACTIVE)
        if periodicals is None:            
            periodicals = Periodical.allPeriodicals()
            periodicals = filter(lambda per: per.active, periodicals)
            memcache.set(settings.MEM_PERIODICALS_ACTIVE, periodicals)        
        return periodicals         
    
    # gets a preformatted list of active feeds for UI templates 
    @staticmethod
    def allActiveView():
        perView = memcache.get(settings.MEM_PERIODICALS_VIEW)
        if perView is None:
            periodicals = Periodical.allActive()
            perView = []
            timezone = "Europe/Prague" # fixed timezone for viewing
            for periodical in periodicals:
                
                setattr(periodical, "keyname", periodical.key().name())  
                file = periodical.keyname.replace(" ", "%20")  
                setattr(periodical, "download_link", "<a href=""/download/%s"">%s</a>" % (file, "Download"))               
                                
                last_update = timezones.fromUTC(periodical.last_update, timezone).strftime("%d.%m.%Y %H:%M")                
                setattr(periodical, "updated", last_update)
                
                if periodical.homepage:
                    link = "<a href=""%s"">%s</a>" % (periodical.homepage, periodical.name)
                else:
                    link = periodical.name
                setattr(periodical, "link", link)
                    
                if periodical.filesizeKB:
                    size = "%.1f MB" % (float(periodical.filesizeKB) / 1024)
                else:
                    size = "N/A"
                setattr(periodical, "size", size)

                perView.append(periodical) 
            
            perView.sort(key=lambda periodical: periodical.name.lower())                        
            memcache.set(settings.MEM_PERIODICALS_VIEW, perView)
        
        return perView 
  
    @staticmethod
    def clearCache():
        memcache.delete(settings.MEM_PERIODICALS)
        memcache.delete(settings.MEM_PERIODICALS_ACTIVE)
        memcache.delete(settings.MEM_PERIODICALS_VIEW)     
  
class UserSettings(db.Model):
    user = db.UserProperty()
    timezone = db.StringProperty(required=True)
    email = db.EmailProperty()
    reader_type = db.StringProperty()
    note = db.StringProperty(multiline=True)  
    created = db.DateTimeProperty(auto_now_add=True) 
    modified = db.DateTimeProperty(auto_now=True) 
  
    @staticmethod
    def getCurrentUserSettings():   
        return UserSettings.getUserSettings(users.get_current_user().user_id())
    
    @staticmethod
    def getUserSettings(user_id):
        CACHE_KEY = "USER_SET_" + user_id
        settings = memcache.get(CACHE_KEY)
        if settings is None:
            settings = UserSettings.get_by_key_name(users.get_current_user().user_id())
            
        memcache.set(CACHE_KEY, settings)
        return settings    
    
    @staticmethod    
    def clearCache():
        user_id = users.get_current_user().user_id()
        memcache.delete("USER_SET_" + user_id)
        memcache.delete("USER_SUB_" + user_id)
        
import timezones
class Subscription(db.Model):
    user_settings = db.ReferenceProperty(UserSettings, required=True, collection_name="subscriptions")
    periodical = db.ReferenceProperty(Periodical, required=True)
    schedule_weekday = db.StringProperty()
    schedule_weekend = db.StringProperty()
    
    schedule_0 = db.StringProperty()
    schedule_1 = db.StringProperty()
    schedule_2 = db.StringProperty()
    schedule_3 = db.StringProperty()
    schedule_4 = db.StringProperty()
    schedule_5 = db.StringProperty()
    schedule_6 = db.StringProperty()
   
    send_last = db.DateTimeProperty()
    send_next = db.DateTimeProperty()
    
    @staticmethod
    def getCurrentUserSubscriptions():   
        return Subscription.getUserSubscriptions(users.get_current_user().user_id())
    
    @staticmethod
    def getUserSubscriptions(user_id): 
        CACHE_KEY = "USER_SUB_" + user_id
        subscriptions = memcache.get(CACHE_KEY)
        if subscriptions is None:
            user_settings = UserSettings.getUserSettings(user_id)
            if user_settings is not None:
                subscriptions = user_settings.subscriptions.fetch(100)
                memcache.set(CACHE_KEY, subscriptions)
        return subscriptions            

    @staticmethod
    def getCurrentUserSubscriptionsView():   
        return Subscription.getUserSubscriptionsView(users.get_current_user().user_id())
    
    @staticmethod
    def getUserSubscriptionsView(user_id):
        subscriptions = Subscription.getUserSubscriptions(user_id)
        user_settings = UserSettings.getUserSettings(user_id)
         
        subscriptionsView = []
        for periodical in Periodical.allActiveView():
            # for given periodical, add subscription info, if available
            if not subscriptions is None:
                #key = db.Key.from_path("Periodical", subscription["keyname"])
                results = filter(lambda sub: sub.periodical.key().name() == periodical.keyname, subscriptions)
                if results:
                    for i in range(7):
                        propName = 'schedule_%s' % str(i)
                        setattr(periodical, propName, getattr(results[0], propName)) 
                    
                    timezone = user_settings.timezone                    
                    send_last = results[0].send_last
                    if send_last is not None:
                        send_last = timezones.fromUTC(send_last, timezone).strftime("%d.%m. %H:%M")
                        setattr(periodical, "send_last", send_last)   
                                          
                    send_next = results[0].send_next
                    if send_next is not None:
                        send_next = timezones.fromUTC(send_next, timezone).strftime("%d.%m. %H:%M")
                        setattr(periodical, "send_next", send_next) 
                         
            subscriptionsView.append(periodical)      
        return subscriptionsView     
    
    def getTimes(self, schedule): 
        if schedule is None:
            return None       
        scheduledTimes = []
        timestrings = string.split(schedule, ",")
        for timestring in timestrings:
            parts = string.split(timestring, ":")
            hours = int(parts[0])
            if len(parts) > 1:
                minutes = int(parts[1])
            else:
                minutes = 0
            scheduledTime = time(hour=hours, minute=minutes)
            
            # now we need to convert the time to UTC time since schedules are stored in user TZ
            tempDate = datetime.combine(datetime.now().date(), scheduledTime)
            tempDate = timezones.toUTC(tempDate, self.user_settings.timezone)
            # add just the converted time
            scheduledTimes.append(tempDate.time())
        
        scheduledTimes.sort()
        return scheduledTimes
    
    def findNextTime(self, scheduledTimes):       
        for scheduledTime in scheduledTimes:
            if scheduledTime > datetime.now().time():
                return scheduledTime
        return None    
    
    def setNextSend(self):
        # datetime.weekday(): Return the day of the week as an integer, where Monday is 0 and Sunday is 6        
        self.send_next = None # reset
        now = datetime.now()  
        
        # fill daily schedules              
        day_schedules = []
        
        for i in range(7):
            propName = 'schedule_%s' % str(i)
            propValue = getattr(self, propName)
            day_schedule = self.getTimes(propValue)
            day_schedules.append(day_schedule) 
        
        if not day_schedules:
            return # no date setup
            
        #try today and next 6 days to find closest scheduled run
        for i in range(7):
            nowTime = now.time()
            d = timedelta(days=i)
            nextDate = now + d 
            currentSchedule = day_schedules[nextDate.weekday()] # use weekday to return proper list item
            
            if currentSchedule is None:
                continue
            
            if i == 0:
                # for today, we need to start looking at current time
                nextTime = self.findNextTime(currentSchedule)                
            else:
                # for other days, just pick the earliest time
                nextTime = min(currentSchedule)
                
            if not nextTime is None:
                # next delivery datetime found                
                self.send_next = datetime.combine(nextDate, nextTime)
                break        
            
class MailQueue(db.Model):
    email = db.EmailProperty(required=True)
    user_settings = db.ReferenceProperty(UserSettings, required=True, collection_name="queue")
    periodical = db.ReferenceProperty(Periodical, required=True)
    inserted = db.DateTimeProperty(auto_now_add=True)
    delivered = db.DateTimeProperty()
    
class PeriodicalDownloadStats(db.Model):
    downloaded = db.DateTimeProperty(required=True, auto_now_add=True)
    user = db.UserProperty()

class PeriodicalStats(db.Model):
    periodical = db.ReferenceProperty(Periodical, required=True)
    date = db.DateProperty(required=True, auto_now_add=True) 
    subscribers = db.IntegerProperty()
    mails_sent = db.IntegerProperty()
    files_downloaded = db.IntegerProperty()
    generated_date = db.DateTimeProperty(auto_now=True) 
    
    @staticmethod
    def getStats(date):  
        statsView = memcache.get(settings.MEM_STATS)
        if statsView is None:          
            timezone = "Europe/Prague"        
            statsView = []
            
            periodicals = Periodical.allActiveView()
            for periodical in periodicals:
                perKey = db.Key.from_path('Periodical', periodical.keyname)
                query = PeriodicalStats.all()
                query.ancestor(perKey)
                query.order('-date') # get the newest available
                
                stat = query.get()
                if stat is None:
                    continue
                                
                statView = {}
                statView["periodical"] = periodical.link
                statView["name"] = periodical.name
                statView["subscribers"] = stat.subscribers
                statView["updated"] = timezones.fromUTC(stat.generated_date, timezone).strftime("%d.%m.%Y %H:%M")
                statsView.append(statView)
            
            statsView.sort(key=lambda per: per["subscribers"], reverse=True) 
            memcache.set(settings.MEM_STATS, statsView)
            
        return statsView    
    
    @staticmethod
    def updatePeriodicalsStats(periodical):
        """
        Update statistics for periodical
        """        
        subs_query = Subscription.all()
        subs_query.ancestor(periodical)
        subs_count = subs_query.count()
        
        # sent emails and downloads in last 24 hours
        d = timedelta(hours=-24)
        from_date = datetime.now() + d
        
        download_query = PeriodicalDownloadStats.all()
        download_query.ancestor(periodical)
        download_query.filter("downloaded >= ", from_date) 
        download_count = download_query.count()        

        mail_query = MailQueue.all()
        mail_query.ancestor(periodical)
        mail_query.filter("inserted >= ", from_date) 
        mail_count = mail_query.count()

        logging.debug("Updating periodical statistics for %s, subscribers %i, downloaded %i, sent %i" % (periodical.name, subs_count, download_count, mail_count))
  
        today = datetime.now().date()
        key = db.Key.from_path('Stats', today.strftime("%d.%m.%Y"), parent=periodical.key())
        
        periodical = PeriodicalStats.get_or_insert(key_name = key.name(), 
                            parent = periodical,
                            periodical = periodical,
                            date = today)
        periodical.subscribers = subs_count
        periodical.files_downloaded = download_count
        periodical.mails_sent = mail_count
        periodical.put()

class WIPsite(db.Model):  
    name = db.StringProperty(required=True)   
    homepage = db.LinkProperty(required=True)    
    user = db.UserProperty()
    contact = db.EmailProperty()
    status = db.StringProperty()   
    created = db.DateTimeProperty(auto_now_add=True) 
    modified = db.DateTimeProperty(auto_now=True) 
    active = db.BooleanProperty(required=True, default=True)
    note = db.StringProperty() 
    
    @staticmethod
    def clearCache():
        memcache.delete(settings.MEM_WISHLIST_VIEW)
    
    @staticmethod
    def allView():
        wishView = memcache.get(settings.MEM_WISHLIST_VIEW)
        if wishView is None:
            wishView = []
            sites = WIPsite.all()    
            sites.filter("active = ", True)      
            timezone = "Europe/Prague" # fixed timezone for viewing       
            for site in sites: 
                
                # add link
                if site.homepage:
                    link = "<a href=""%s"">%s</a>" % (site.homepage, site.name)
                else:
                    link = site.name
                setattr(site, "link", link)
                
                #convert and format datetimes
                created = timezones.fromUTC(site.created, timezone).strftime("%d.%m.%Y %H:%M") 
                setattr(site, "createdf", created)
                modified = timezones.fromUTC(site.modified, timezone).strftime("%d.%m.%Y %H:%M")
                setattr(site, "modifiedf", modified) 
                
                wishView.append(site)
                
            wishView.sort(key=lambda site: site.name.lower())                        
            memcache.set(settings.MEM_WISHLIST_VIEW, wishView)
        
        return wishView 
    
class Book(db.Model):  
    name = db.StringProperty(required=True) 
    active = db.BooleanProperty(required=True, default=True)
    filename = db.StringProperty(required=True)
    filesizeKB = db.IntegerProperty()
    created = db.DateTimeProperty(auto_now_add=True) 
    modified = db.DateTimeProperty(auto_now=True)
    
class BookDownloadStats(db.Model):
    downloaded = db.DateTimeProperty(required=True, auto_now_add=True)
    user = db.UserProperty()
    
    @staticmethod
    def getStats():  
        statsView = memcache.get(settings.MEM_STATS_BOOKS)
        if statsView is None: 
            statsView = []
            books = Book.all()
            for book in books:
                bookKey = db.Key.from_path('Book', book.key().name())
                query = BookDownloadStats.all()
                query.ancestor(bookKey)
                count = query.count(1000)
                                
                statView = {}
                statView["book"] = book.name
                statView["downloaded"] = count
                statsView.append(statView)
            
            statsView.sort(key=lambda per: per["downloaded"], reverse=True) 
            memcache.set(settings.MEM_STATS_BOOKS, statsView)
            
        return statsView    
    